#ifndef _Plugin_H_
//#define _Plugin_H_

#include <vector>
#include <string>
#include <regex.h>
#include <sys/types.h>
#include <dlfcn.h>
#include "Webcrawler.hpp"
#include "Configurator.hpp"
#include "BloomFilter.hpp"
#include "Log.hpp"

//插件接口
class Plugin
{
public:
    //析构
    virtual ~Plugin(void) {}
    //纯虚函数，子类根据不同的插件功能给出具体实现
    virtual bool init(WebCrawler* app) = 0; // 初始化;
    //纯虚函数，子类根据不同插件的具体功能给出具体实现
    virtual bool handler(void* arg) = 0; // 插件处理
};




//插件管理器
class PluginMngr
{
private:
    std::vector<Plugin *> _urlPlugins;    // 统一资源定位符插件向量
    std::vector<Plugin *> _headerPlugins; // 超文本传输协议响应包头插件向量
    std::vector<Plugin *> _htmlPlugins;   // 超文本标记语言插件向量
public:
    // 加载插件
    void load(void)
    {
        // 遍历配置器中的插件列表向量
        for (std::vector<std::string>::const_iterator it = g_app->_cfg._loadPlugins.begin();
             it != g_app->_cfg._loadPlugins.end();
             ++it)
        {
            // 利用配置器中的插件路径，构造插件共享库路径：
            // <插件路径>/<插件名>.so，例如：
            // ../plugins/MaxDepth.so
            // ../plugins/DomainLimit.so，等等
            std::string path = StrKit::strcat(g_app->_cfg._pluginsPath.c_str(), "/",it->c_str(), ".so", NULL);

            // 记录调试日志
            // g_app->_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
            //                     "加载\"%s\"插件", path.c_str());
            lg(Debug, "socker error, %s: %d", strerror(errno), errno);

            // 打开插件共享库
            void *handle = dlopen(path.c_str(), RTLD_GLOBAL | RTLD_NOW);
            // 若失败
            if (!handle)
            {   
                lg(Error, "socker error, %s: %d", strerror(errno), errno);
                // 记录一般错误日志
                // g_app->m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
                //                     "dlopen: %s", dlerror());

                // 利用插件名，构造插件对象名：
                // g_plugin<插件名>，例如：
                // g_pluginMaxDepth
                // g_pluginDomainLimit，等等
            }
            std::string symbol = "g_plugin";
            symbol += *it;

            // 从插件共享库中获取插件对象地址，并转换为其基类类型的指针
            Plugin *plugin = (Plugin *)dlsym(handle, symbol.c_str());
            // 若失败
            if (!plugin)
                // 记录一般错误日志
                // g_app->m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
                //                     "dlsym: %s", dlerror());
            lg(Error, "socker error, %s: %d", strerror(errno), errno);
                // 通过插件基类调用插件子类的初始化接口
            plugin->init(g_app);
        }
    }

    /// @brief 注册统一资源定位符插件
    /// @remark 统一资源定位符插件通过此接口将其自身注册到插件管理器中
    //< [in] 统一资源定位符插件
    void registerUrlPlugin(Plugin *plugin)
    {
        // 将统一资源定位符插件指针压入统一资源定位符插件向量
        _urlPlugins.push_back(plugin);
    }
    // 注册超文本传输协议响应包头插件
    // 超文本传输协议响应包头插件通过此接口将其自身注册到插件管理器中
    // 插件报头
    //  将超文本传输协议响应包头插件指针压
    //  入超文本传输协议响应包头插件向量
    void registerHeaderPlugin(Plugin *plugin) { _headerPlugins.push_back(plugin); }

    // 注册超文本标记语言插件
    // 超文本标记语言插件通过此接口将其自身注册到插件管理器中
    // 超文本标记语言插件
    // 将超文本标记语言插件指针压入超文本标记语言插件向量
    void registerHtmlPlugin(Plugin *plugin) { _htmlPlugins.push_back(plugin); }
    // 调用统一资源定位符插件处理函数
    // true  成功
    // false 失败
    // 依次调用每个统一资源定位符插件处理函数，只要有一个失败即返回失败
    // 输入输出插件
    bool invokeUrlPlugins(void *arg) const
    {
        // 遍历统一资源定位符插件向量
        for (std::vector<Plugin *>::const_iterator it = _urlPlugins.begin();
             it != _urlPlugins.end();
             ++it)
            // 依次调用每个统一资源定位符插件的插件处理接口，若失败
            if (!(*it)->handler(arg))
                return false; // 返回失败

        return true; // 返回成功
    }

    // 调用超文本传输协议响应包头插件处理函数
    // true  成功
    // false 失败
    // 依次调用每个超文本传输协议响应包头插件处理函数，只要有一个失败即返回失败
    /// 插件参数
    bool invokeHeaderPlugins(void *arg) const
    {
        // 遍历超文本传输协议响应包头插件向量
        for (std::vector<Plugin *>::const_iterator it = _headerPlugins.begin();
             it != _headerPlugins.end(); ++it)
            // 依次调用每个超文本传输协议响应
            // 包头插件的插件处理接口，若失败
            if (!(*it)->handler(arg))
                // 返回失败
                return false;
        // 返回成功
        return true;
    }

    // 调用超文本标记语言插件处理函数
    // 依次调用每个超文本标记语言插件处理函数，忽略其成功失败
    // 插件参数
    void invokeHtmlPlugins(void *arg) const
    {
        // 遍历超文本标记语言插件向量
        for (std::vector<Plugin *>::const_iterator it = _htmlPlugins.begin();
             it != _htmlPlugins.end(); ++it)
            // 依次调用每个超文本标记语言插件
            // 的插件处理接口，不管成功失败
            (*it)->handler(arg);
    }
};



// DomainLimit g_pluginDomainLimit; // 域名限制插件对象
// WebCrawler* g_app;// 应用程序对象


// 域名限制插件
class DomainLimit : public Plugin
{
    std::vector<std::string> _includePrefixes;  // 包含前缀字符串向量
    std::vector<std::string> _excludePrefixes; // 排除前缀字符串向量
private:
    // 插件初始化
    // true  成功
    // false 失败
    // 根据域名限制插件的功能实现基类中的虚函数
    bool init(WebCrawler *app)
    {
        // 以统一资源定位符插件的身份
        // 注册到应用程序对象的插件管理器中
        (g_app = app)->_pluginMngr.registerUrlPlugin(this);
        // 拆分配置器中的包含前缀字符串，以逗号为分隔符，不限拆分次数
        _includePrefixes = StrKit::split(g_app->_cfg._includePrefixes, ",", 0);
        // 拆分配置器中的排除前缀字符串，以逗号为分隔符，不限拆分次数
        _excludePrefixes = StrKit::split(g_app->_cfg._excludePrefixes, ",", 0);
        // 返回成功
        return true;
    }

    // true  成功
    // false 失败
    // 根据域名限制插件的功能实现基类中的虚函数
    // 插件参数
    bool handler(void *arg) // 插件处理
    {
        // 原始统一资源定位符
        RawUrl *rawUrl = static_cast<RawUrl*>(arg);

        // 若是种子链接(链接深度为0)或非超文本标记语言 返回成功，抓取该统一资源定位符 
        if (!rawUrl->_depth || rawUrl->_type != RawUrl::ETYPE_HTML) return true;
    
        // 字符串向量只读迭代器
        std::vector<std::string>::const_iterator it;

        // 统一资源定位符字符串以哪个包含前缀开头
        //auto it = _includePrefixes.begin();
        for (it = _includePrefixes.begin();it != _includePrefixes.end() && rawUrl->_strUrl.find(*it, 0);++it);

        // 若没有与统一资源定位符字符串匹配的包含前缀
        if (!_includePrefixes.empty() && it == _includePrefixes.end())
        {
            lg(Warning, "socker error, %s: %d", strerror(errno), errno);
            // // 记录警告日志
            // g_app->m_log.printf (Log::LEVEL_WAR, __FILE__, __LINE__,
            //     "不抓不在包含集\"%s\"中的统一资源定位符\"%s\"",
            //     g_app->m_cfg.m_includePrefixes.c_str (),
            //     rawUrl->m_strUrl.c_str ());

            // 返回失败，不抓该统一资源定位符
            return false;
        }

        // 统一资源定位符字符串以哪个排除前缀开头
        for (it = _excludePrefixes.begin(); it != _excludePrefixes.end() && rawUrl->_strUrl.find(*it, 0);++it);

        // 若存在与统一资源定位符字符串匹配的排除前缀
        if (it != _excludePrefixes.end())
        {
            lg(Warning, "socker error, %s: %d", strerror(errno), errno);
            // 记录警告日志
            // g_app->m_log.printf (Log::LEVEL_WAR, __FILE__, __LINE__,
            // 	"不抓排除集\"%s\"中的统一资源定位符\"%s\"",
            // 	g_app->m_cfg.m_excludePrefixes.c_str (),
            // 	rawUrl->m_strUrl.c_str ());
            // 返回失败，不抓该统一资源定位符
            return false;
        }

        // 返回成功，抓取该统一资源定位符
        return true;
    }
};


//超文本传输协议响应包头过滤器插件
class HeaderFilter : public Plugin
{
private:
    // 根据超文本传输协议响应包头过滤器插件的功能实现基类中的虚函数
    bool init(WebCrawler *app) // 初始化
    {
        // 以超文本传输协议响应包头插件的身份
        // 注册到应用程序对象的插件管理器中
        (g_app = app)->_pluginMngr.registerHeaderPlugin(this);
        // 返回成功
        return true;
    }

    /// @note 根据超文本传输协议响应包头过滤器插件的功能实现基类中的虚函数
    bool handler(void *arg) // 处理
    {
        // 超文本传输协议响应包头
        HttpHeader *header = static_cast<HttpHeader *>(arg);

        // 若超文本传输协议响应状态码不在合理区间[200,300)之内
        if (header->_statusCode < 200 || 300 <= header->_statusCode)
        {
            lg(Warning, "socker error, %s: %d", strerror(errno), errno);
            // 记录调试日志
            // g_app->_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
            //                     "超文本传输协议响应状态码%d不在合理区间[200,300)之内",
            //                     header->m_statusCode);
            // 返回失败，不再接收该响应包包体
            return false;
        }

        // 若超文本传输协议响应内容类型不是超文本标记语言
        if (header->_contentType.find("text/html", 0) == std::string::npos)
        {
            // 字符串向量只读迭代器
            std::vector<std::string>::const_iterator it;

            // 超文本传输协议响应内容类型与
            // 配置器中的哪个接受类型匹配
            for (it = g_app->_cfg._acceptTypes.begin();
                 it != g_app->_cfg._acceptTypes.end() &&
                 header->_contentType.find(*it, 0) == std::string::npos;
                 ++it)
                ;

            // 若超文本传输协议响应内容类型与
            // 配置器中任何接受类型都不匹配
            if (it == g_app->_cfg._acceptTypes.end())
            {
            lg(Warning, "socker error, %s: %d", strerror(errno), errno);

                // 记录调试日志
                // g_app->_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
                //                     "超文本传输协议响应类型%s不在可接受范围之内",
                //                     header->m_contentType.c_str());
                // 返回失败，不再接收该响应包包体
                return false;
            }
        }

        // 返回成功，继续接收该响应包包体
        return true;
    }
};


/// @brief 最大深度插件
class MaxDepth : public Plugin
{
private:
    // 根据最大深度插件的功能实现基类中的虚函数
    // 应用程序对象 app
    bool init(WebCrawler *app) // 初始化
    {
        // 以统一资源定位符插件的身份
        // 注册到应用程序对象的插件管理器中
        (g_app = app)->_pluginMngr.registerUrlPlugin(this);
        // 返回成功
        return true;
    }

    // 根据最大深度插件的功能实现基类中的虚函数
    bool handler(void *arg) // 插件处理
    {
        // 原始统一资源定位符
        RawUrl *rawUrl = static_cast<RawUrl *>(arg);

        // 若配置器中的最大递归深度有效，且
        // 该统一资源定位符的链接深度已超限
        if (0 <= g_app->_cfg._maxDepth &&
            g_app->_cfg._maxDepth < rawUrl->_depth)
        {
            // 记录警告日志
            // g_app->m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
            //                     "不抓过深(%d>%d)的统一资源定位符\"%s\"", rawUrl->m_depth,
            //                     g_app->m_cfg.m_maxDepth, rawUrl->m_strUrl.c_str());
            // 返回失败，不抓该统一资源定位符
            return false;
        }
        // 返回成功，抓取该统一资源定位符
        return true;
    }
};

// 超文本标记语言文件存储插件
class SaveHTMLToFile : public Plugin
{
private:
    // 根据超文本标记语言文件存储插件的功能实现基类中的虚函数
    bool init(WebCrawler *app) // 初始化插件
    {
        // 以超文本标记语言插件的身份
        // 注册到应用程序对象的插件管理器中
        (g_app = app)->_pluginMngr.registerHtmlPlugin(this);
        // 返回成功
        return true;
    }

    /// @note 根据超文本标记语言文件存储插件的功能实现基类中的虚函数
    bool handler(void *arg) // 插件处理
    {
        // 超文本传输协议响应
        HttpResponse *res = static_cast<HttpResponse *>(arg);

        // 若超文本传输协议响应内容类型不是超文本标记语言
        if (res->_header._contentType.find("text/html", 0) == std::string::npos)
            // 返回失败
            return false;

        // 将解析统一资源定位符转换为文件名字符串
        std::string filename = res->_dnsUrl.toFilename();
        // 根据文件名打开超文本标记语言文件输出流
        std::ofstream ofs(filename.c_str(), std::ios::binary);
        // 若失败
        if (!ofs)
        {
            // 记录警告日志
            // g_app->m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
            //                     "打开文件%s失败: %s", filename.c_str(),
            //                     strerror(errno));
            // 返回失败
            return false;
        }

        // 将超文本传输协议响应包体写入超文本标记语言文件输出流，若失败
        if (!ofs.write(res->_body, res->_len))
        {
            lg(Warning, "socker error, %s: %d", strerror(errno), errno);

            // 记录警告日志
            // g_app->m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
            //                     "写入文件%s失败: %s", filename.c_str(), strerror(errno));
            // // 关闭超文本标记语言文件输出流
            ofs.close();
            // 删除超文本标记语言文件
            unlink(filename.c_str());
            // 返回失败
            return false;
        }

        // 记录调试日志
        // g_app->m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
        //                     "文件%s保存成功", filename.c_str());
        lg(Debug, "socker error, %s: %d", strerror(errno), errno);

        // 关闭超文本标记语言文件输出流
        ofs.close();
        // 返回成功
        return true;
    }
};


// 图像文件存储插件
class SaveImageToFile : public Plugin
{
private:
    // 根据图像文件存储插件的功能实现基类中的虚函数
    bool init(WebCrawler *app) // 初始化
    {
        // 以超文本标记语言插件的身份
        // 注册到应用程序对象的插件管理器中
        (g_app = app)->_pluginMngr.registerHtmlPlugin(this);
        // 返回成功
        return true;
    }

    // 根据图像文件存储插件的功能实现基类中的虚函数
    bool handler(void *arg) // 插件处理
    {
        // 超文本传输协议响应
        HttpResponse *res = static_cast<HttpResponse *>(arg);

        // 若超文本传输协议响应内容类型是超文本标记语言
        if (res->_header._contentType.find("text/html", 0) != std::string::npos)
        {
            // 正则表达式
            regex_t ex;

            // 编译正则表达式：<img [^>]*src="\s*\([^ >"]*\)\s*"
            //     \s - 匹配任意空白字符(空格、制表、换页等)
            //      * - 重复前一个匹配项任意次
            //     \( - 子表达式左边界
            //     \) - 子表达式右边界
            // [^ >"] - 匹配任意不是空格、大于号和双引号的字符
            int error = regcomp(&ex, "<img [^>]*src=\"\\s*\\([^ >\"]*\\)\\s*\"", 0);
            // 若失败
            if (error)
            {
                // 错误信息缓冲区
                char errInfo[1024];
                // 获取正则表达式编译错误信息
                regerror(error, &ex, errInfo, sizeof(errInfo) / sizeof(errInfo[0]));
                // 记录一般错误日志
                // g_app->m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
                //                     "regcomp: %s", errInfo);
            lg(Error, "socker error, %s: %d", strerror(errno), errno);

            }

            // 超文本标记语言页面内容字符串
            char const *html = res->_body;
            // 匹配集合
            regmatch_t match[2];

            // 在超文本标记语言页面内容字符串中，
            // 查找所有与正则表达式匹配的内容
            while (regexec(&ex, html, sizeof(match) / sizeof(match[0]), match, 0) != REG_NOMATCH)
            {
                // regex : <img [^>]*src="\s*\([^ >"]*\)\s*"
                // html  : ...<img src="  /images/potalaka.jpg  "...
                //            |           |<----match[1]---->|  |
                //            |         rm_so              rm_eo|
                //            |<--------------match[0]--------->|
                //          rm_so                             rm_eo

                // 匹配子表达式的内容首地址
                html += match[1].rm_so;
                // 匹配子表达式的内容字符数
                size_t len = match[1].rm_eo - match[1].rm_so;
                // 匹配子表达式的内容字符串，即图像源中的统一资源定位符
                std::string strUrl(html, len);
                // 移至匹配主表达式的内容后，以备在下一轮循环中继续查找
                html += len + match[0].rm_eo - match[1].rm_eo;

                // 若添加域名失败
                if (!res->_dnsUrl.attachDomain(strUrl))
                    // 继续下一轮循环
                    continue;

                // 记录调试日志
                // g_app->m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
                //                     "抽取到一个深度为%d的统一资源定位符\"%s\"",
                //                     res->m_dnsUrl.m_depth, strUrl.c_str());
            lg(Debug, "socker error, %s: %d", strerror(errno), errno);


                // 若规格化失败
                if (!RawUrl::normalized(strUrl))
                {
                    // 记录警告日志
                    // g_app->_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
                    //                     "规格化统一资源定位符\"%s\"失败", strUrl.c_str());
                    // 继续下一轮循环
                    continue;
                }

                // 压入原始统一资源定位符队列
                g_app->_urlQueues.pushRawUrl(RawUrl(strUrl, RawUrl::ETYPE_IMAGE, res->_dnsUrl._depth));
            }

            // 释放正则表达式
            regfree(&ex);
        }
        // 否则，若超文本传输协议响应内容类型是图像
        else if (res->_header._contentType.find("image", 0) != std::string::npos)
        {
            // 将解析统一资源定位符转换为文件名字符串
            std::string filename = res->_dnsUrl.toFilename();

            // 根据文件名打开图像文件输出流
            std::ofstream ofs(filename.c_str(), std::ios::binary);
            // 若失败
            if (!ofs)
            {
                // 记录警告日志
                // g_app->m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
                //                     "打开文件%s失败: %s", filename.c_str(),
                //                     strerror(errno));
                // 返回失败
                return false;
            }

            // 将超文本传输协议响应包体写入图像文件输出流，若失败
            if (!ofs.write(res->_body, res->_len))
            {
                // 记录警告日志
                // g_app->m_log.printf(Log::LEVEL_WAR, __FILE__, __LINE__,
                //                     "写入文件%s失败: %s", filename.c_str(), strerror(errno));
                // 关闭图像文件输出流
                ofs.close();
                // 删除图像文件
                unlink(filename.c_str());
                // 返回失败
                return false;
            }

            // 记录调试日志
            // g_app->_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
            //                     "文件%s保存成功", filename.c_str());
            lg(Debug, "socker error, %s: %d", strerror(errno), errno);

            // 关闭图像文件输出流
            ofs.close();
        }

        // 返回成功
        return true;
    }
};

#endif //_Plugin_H_